package com.sinai.mshab.client.ui.aggregate;

import java.util.ArrayList;
import java.util.HashMap;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.sinai.mshab.client.services.Service;
import com.sinai.mshab.client.services.ServiceAsync;
import com.sinai.mshab.client.util.Answer;
import com.sinai.mshab.client.util.AnswerCollection;
import com.sinai.mshab.client.util.FieldName;
import com.sinai.mshab.client.ui.Key;

/**
 * This class stores data for form submission - All widgets send their data
 * here, and once it is all in, FormData will send it to the server.
 * 
 * This class stores data for form population - Upon request this class will
 * retreive all data relevant to the current key and populate the provided page
 * container with the retreived data.
 * 
 * This class stores the Key, which uniquely identifies and links all relevant
 * information to the current program entity
 * 
 * This class stores the structure of the database - The class
 * AdvancedFilteredTable cannot function without knowing the structure of the
 * database. This class retreives the db structure from the database and makes
 * it available to AdvancedFilteredTable.
 * 
 * This class stores the blacklist of table names and columns - The names in
 * these lists will not be available in AdvancedFilteredTable.
 * 
 * There can only be one instance of this class as it implements the singleton
 * pattern. Please use FormData.getInstance() to grab the object.
 * 
 * @author serge
 */
public class FormData implements DataReceiver {

	/**
	 * This hashmap stores the form data, one answer collection per table name.
	 * each answer collection has 1 answer per row of that table given
	 * primary_key criteria.
	 */
	private static HashMap table_to_collection = new HashMap();

	/**
	 * Stores the available table names in the database
	 */
	private static ArrayList table_names = new ArrayList();

	/**
	 * The table names which are in this list are not shown in the filter
	 * widgets
	 */
	private static ArrayList table_names_blacklist = new ArrayList();

	/**
	 * The column names which are in this list are not shown in the filter
	 * widgets
	 */
	private static ArrayList column_names_blacklist = new ArrayList();

	/**
	 * This hashmap stores the arraylist of column names corresponding to the
	 * key, which is the table name.
	 */
	private static HashMap table_to_columns = new HashMap();

	/** This answer collections keeps track of all answers sent to this form data */
	private static AnswerCollection answer_collection = new AnswerCollection();

	/** The key associated with each record */
	private static Key key = new Key();

	private static boolean populated = false;

	/**
	 * The FormData instance.
	 */
	private static FormData instance = new FormData();

	private static String servlet_url;

	/** Rememebers if we already retrieved the database structure of not */
	private static boolean db_structure_retreived;

	private PageContainer container_to_populate;

	private Page page_to_populate;

	/**
	 * Private constructor to insure that no one can create another instance.
	 */
	private FormData() {

	}

	/**
	 * @return the servlet_url
	 */
	public static String getServletUrl() {
		return servlet_url;
	}

	/**
	 * @return the servlet_url
	 */
	public static void setServletUrl(String url) {
		servlet_url = url;
	}

	/**
	 * Returns the unique and only instance of FormData.
	 * 
	 * @return the unique and only instance of FormData.
	 */
	public static FormData getInstance() {
		return instance;
	}

	/**
	 * Returns the answer collection corresponding to the provided table_name
	 * 
	 * @param table_name
	 *            the table name for which to return the answer collection
	 * @return the answer collection corresponding to the provided table_name
	 */
	public static AnswerCollection getAnswerCollection(String table_name) {
		return (AnswerCollection) table_to_collection.get(table_name);
	}

	/**
	 * Returns the answer corresponding to the provided table_name
	 * 
	 * @param table_name
	 *            the table name for which to return the answer collection
	 * @return the answer corresponding to the provided table_name
	 */
	public static Answer getData(String table_name) {
		if (table_to_collection.containsKey(table_name)) {
			AnswerCollection ac = (AnswerCollection) table_to_collection
					.get(table_name);
			return (Answer) ac.get(0);
		}
		return null;
	}

	/**
	 * Register an answer collection with the provided table name
	 * 
	 * @param table_name
	 *            table name corresponding to the data
	 * @param data
	 *            the data corresponding to the table name
	 */
	public static void addData(String table_name, AnswerCollection data) {
		table_to_collection.put(table_name, data);
	}

	/**
	 * Register a hashmap containing table-to-answercollection data
	 * 
	 * @param map
	 *            the hashmap already containig table-to-answercollection data
	 */
	public static void addData(HashMap map) {
		table_to_collection = map;
	}

	/**
	 * Forgets about all previous data.
	 */
	public static void reset() {
		populated = false;
		table_to_collection.clear();
		answer_collection = new AnswerCollection();
	}

	/**
	 * Creates the async request to the server to fetch the patient information
	 * 
	 * @param container_to_populate
	 *            the page container to populate after all the data is in.
	 */
	public void populateFormData(PageContainer container_to_populate) {
		this.container_to_populate = container_to_populate;
		getService().retrieveData(FormData.getKey(), new RetrieveCallBack());
		populated = true;
	}
	
	/**
	 * Creates the async request to the server to fetch the patient information
	 * 
	 * @param container_to_populate
	 *            the page container to populate after all the data is in.
	 */
	public void populateFormData(Page page_to_populate) {
		this.page_to_populate = page_to_populate;
		getService().retrieveData(FormData.getKey(), new RetrieveCallBackPage());
		populated = true;
	}

	/**
	 * Returns the service set up by the application
	 * 
	 * @return the service set up by the application
	 */
	public ServiceAsync getService() {
		ServiceAsync formsService = (ServiceAsync) GWT.create(Service.class);
		ServiceDefTarget target = (ServiceDefTarget) formsService;
		String relativeUrl = GWT.getModuleBaseURL() + FormData.getServletUrl();
		target.setServiceEntryPoint(relativeUrl);
		return formsService;
	}

	/**
	 * A method for recieving an answer object that is stored in this component.
	 * 
	 * @param answer
	 *            the answer to store.
	 */
	public void sendData(Answer answer) {
		answer_collection.add(answer);
	}

	/**
	 * Sends the data to the server.
	 */
	public void submitData() {
		answer_collection.pack();
		ServiceAsync formsService = (ServiceAsync) GWT.create(Service.class);
		ServiceDefTarget target = (ServiceDefTarget) formsService;
		String relativeUrl = GWT.getModuleBaseURL() + getServletUrl();
		target.setServiceEntryPoint(relativeUrl);
		formsService.sendAnswer(answer_collection, getKey(),
				new SubmitCallBack());
	}

	/**
	 * @param key
	 *            the key to set
	 */
	public static void setKey(Key key) {
		FormData.key = key;
	}

	/**
	 * @return the key
	 */
	public static Key getKey() {
		return key;
	}

	/**
	 * Returns the list of table names available in the database.
	 * 
	 * @return
	 */
	public ArrayList getTableNames() {
		return table_names;
	}

	/**
	 * Returns the arraylist of column names corresponding to the given table
	 * name
	 * 
	 * @param table_name
	 *            the table name for which to retrieve the column info
	 * @return the arraylist of column names corresponding to the given table
	 *         name
	 */
	public static ArrayList getColumns(String table_name) {
		return (ArrayList) table_to_columns.get(table_name);
	}

	/**
	 * Retreives the list of available tables and their columns from the server.
	 */
	public void getDbStructure() {

		if (!db_structure_retreived) {
			getService().getTables(new TablesCallBack());
			getService().getColumnNames(new ColumnCallBack());
			db_structure_retreived = true;
		}
	}

	/**
	 * Empty callback in case we want to do something immediately after sending
	 * data to the server
	 */
	private class SubmitCallBack implements AsyncCallback {
		public void onFailure(Throwable caught) {
			GWT.log("Error ", caught);
			caught.printStackTrace();
		}

		// take action based on success of login attempt
		public void onSuccess(Object result) {
		}
	}

	/**
	 * Callback to store the given db table information
	 */
	private class TablesCallBack implements AsyncCallback {
		public void onFailure(Throwable caught) {
			GWT.log("Error ", caught);
			caught.printStackTrace();
		}

		// store the given table names.
		public void onSuccess(Object result) {
			table_names = (ArrayList) result;
			System.out.println("Form data received table names");
		}
	}

	/**
	 * Callback to store the given db column name info
	 */
	private class ColumnCallBack implements AsyncCallback {
		public void onFailure(Throwable caught) {
			System.out.println("EVERYTHING FAILED!! NO COLUMNS FOR US!!");
			GWT.log("Error ", caught);
			caught.printStackTrace();
		}

		// store the given table names.
		public void onSuccess(Object result) {
			table_to_columns = (HashMap) result;
			System.out
					.println("Form data received column names with #entries = "
							+ table_to_columns.size());
		}
	}

	/**
	 * Adds the given table to the blacklist
	 * 
	 * @param table
	 *            the table to be blacklisted
	 */
	public static void blacklist_table(String table) {
		table_names_blacklist.add(table);
	}

	/**
	 * Adds the given column to the blacklist
	 * 
	 * @param column
	 *            the column to be blacklisted
	 */
	public static void blacklist_column(String column) {
		table_names_blacklist.add(column);
	}

	/**
	 * Checks if the given table is in the blacklist
	 * 
	 * @param table
	 *            the table to be checked
	 * @return true if the table is blacklisted.
	 */
	public static boolean is_table_blacklisted(String table) {
		return table_names_blacklist.contains(table);
	}

	/**
	 * Checks if the given column is in the blacklist
	 * 
	 * @param column
	 *            the column to be checked
	 * @return true if the column is blacklisted.
	 */
	public static boolean is_column_blacklisted(String column) {
		return table_names_blacklist.contains(column);
	}

	/**
	 * Returns the type of the column
	 * 
	 * @param table
	 *            the table this the required column belongs to
	 * @param column
	 *            the name of the column whos type is to be returned
	 * @return the type of the column.
	 */
	public static String getColumnType(String table, String column) {
		ArrayList columns = getColumns(table);

		// traverse the column objects to find the one we need
		for (int i = 0; i < columns.size(); i++) {
			FieldName field = (FieldName) columns.get(i);
			if (field.getName().equals(column)) {
				return field.getType();
			}
		}

		// if we don't know, assume varchar.
		return "varchar";
	}

	/**
	 * Returns the state of population of the form.
	 * 
	 * @return
	 */
	public static boolean isPopulated() {
		return populated;
	}

	/**
	 * This callback is executed when patient information is retrieved from the
	 * server and stored in FormData If the call is successful, this will
	 * attempt to populate the pages.
	 */
	private class RetrieveCallBack implements AsyncCallback {
		public void onFailure(Throwable caught) {
			GWT.log("Error ", caught);
			caught.printStackTrace();
		}

		// take action based on success of login attempt
		public void onSuccess(Object result) {
			HashMap m = (HashMap) (result);
			FormData.addData(m);
			container_to_populate.populate();
		}
	}
	
	/**
	 * This callback is executed when patient information is retrieved from the
	 * server and stored in FormData If the call is successful, this will
	 * attempt to populate the pages.
	 */
	private class RetrieveCallBackPage implements AsyncCallback {
		public void onFailure(Throwable caught) {
			GWT.log("Error ", caught);
			caught.printStackTrace();
		}

		// take action based on success of login attempt
		public void onSuccess(Object result) {
			HashMap m = (HashMap) (result);
			FormData.addData(m);
			page_to_populate.afterDataAvailable();
		}
	}

	/**
	 * Clears the key in FormData.
	 */
	public static void clearKey() {
		key.getKeys().clear();
	}
}
